home *** CD-ROM | disk | FTP | other *** search
/ Stone Design / Stone Design.iso / Stone_Friends / Wave / WavesWorld / Source / IBPalettes / WW3DKit / WW3DText.m < prev    next >
Encoding:
Text File  |  1995-03-25  |  30.5 KB  |  974 lines

  1. // copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
  2. // see COPYRIGHT for reuse legalities
  3. //
  4.  
  5. /* WW3DText.m
  6.  * Fashioned by Ian Wilkinson.
  7.  * Inception was Sun Jul 17 16:09:45 BST 1994.
  8.  *
  9.  * My History:
  10.  * Hacked by wave on Aug 20.
  11.  * Hacked more by wave on Aug 25 to add archiving.
  12.  * Hacked just a bit more by wave on Aug 25 to fix storage problems
  13.  *     (noted by Ian).
  14.  * Hacked by iw on Sept 6 to provide correct polygon order
  15.  *     for RiGeneralPolygon() (see orderCounters.)
  16.  * Hacked by wave on Sept 12 to add justification; bumped version number to 2
  17.  * Hacked by wave on Sept 13 to add count and objectAt: methods
  18.  * Hacked by wave in early Feb to add conformance to additions to WWRenderable protocol and 
  19.  *     and some nil and NULL setting after frees.       
  20.  * Wildly-controlled engineering by iw on 19 Mar 95 to introduce RiArchiveRecords into
  21.  *     the RIB stream.
  22.  */
  23.  
  24. /* bugs: - loses its shit when handed really complicated characters;
  25.  *         like "WavesWorld" in OutwestHalfFull from Emigre...
  26.  *       - it's polygons, not a trimmed NURBS (although qrman wouldn't actually image it 
  27.  *         right, so...) (probably the correct thing to do is have two reps; this one to
  28.  *         spew at qrman, a trimmed NURBS to spew at prman)
  29.  *       - due to the arbitariness of fonts, this object may be in way over its head
  30.  *         for a given piece of text.  Should do some sort of sanity check...
  31.  *         Not sure where and when to bail, though.  any ideas, ian?
  32.  *       - I can still get it to crash after a while, even using pretty sane stuff,
  33.  *         i.e., "aAbBcC" in Helvetica, a bunch of times.  It usually dies in 
  34.  *         [Storage insertElement:at:]...  any ideas, ian?
  35.  */
  36.  
  37. #import "WW3DText.h"
  38.  
  39. #import "RIBArchiveRecord.h"
  40. #import "RIBGeneralPolygon.h"
  41. #import "WW3DTextWraps.h"
  42. #import "RIBCommandList.h"
  43. #import "RIBTransformBegin.h"
  44. #import "RIBTransformEnd.h"
  45. #import "RIBTranslate.h"
  46. #import "RIBScale.h"
  47. #import "WWSample.h"
  48. #import "WW3DAttributeState.h"
  49.  
  50. // for WW_POINT definition...
  51. #import "WWEveParser.h"
  52.  
  53. // for isspace()
  54. #import <ctype.h>
  55.  
  56. typedef struct {
  57.     int numLoops;
  58.     int numCounters;
  59.     int numCntlPts;
  60. } WW3DTextMetrics;
  61.  
  62. @interface WW3DText(Private)
  63. - getCharPath;
  64. - copyControlPoints:(int)nCntlPts startingAt:(int)outlineIdx to:(Storage *)outlineBuffer;
  65. - orderCounters;
  66. - (Storage *)glyphBoundaries:(WW3DTextMetrics *)metrics :(int)loopIdx :(int)outlineIdx;
  67. - (int)boundaryAfter:(int)idx in:(Storage *)boundaries withNumLoops:(int)numLoops;
  68. - readCharPath:(const char *)theText usingFont:(Font *)theFont;
  69. - generateRIBCommandList;
  70. @end
  71.  
  72. @implementation WW3DText(Private)
  73. static const char *FontIsProtected = "%s is protected, so its outline is entirely private.";
  74. static const int PadComment = 36;    /* 'c' (of ``string'') in fontName fontSize point */
  75.  
  76. static BOOL IsCounter(float x0, float y0, float x1, float y1, float x2, float y2)
  77. {
  78.     return ((x2 - x0)*(y1 - y0) - (x1 - x0)*(y2 - y0) < 0) ? YES : NO;
  79. }
  80.  
  81. - getCharPath
  82. {
  83.     float *charPath;
  84.     int i, len, outlineLen = 0, isProtected, nLoopsInPath, *theLoops;
  85.  
  86.     
  87.     if (!currentText || !currentFont) return self;
  88.     PSWWW3DTextIsFontProtected([currentFont name], [currentFont pointSize], "l", &isProtected);
  89.     if (isProtected) {
  90.     NXRunAlertPanel([NXApp appName], FontIsProtected, NULL, NULL, NULL, [currentFont displayName]);
  91.     return self;
  92.     }
  93.     nLoopsInPath = 0;
  94.     len = strlen(currentText);
  95.     [[textMetrics empty] setNumSlots:len];
  96.     for (i = 0; i < len; i++) {
  97.     WW3DTextMetrics metrics;
  98.     int nLoops, nCntlPts;
  99.  
  100.     PSWWW3DTextMetrics([currentFont name], [currentFont pointSize], ¤tText[i], &nLoops, &nCntlPts);
  101.     metrics.numLoops = nLoops;
  102.     metrics.numCntlPts = nCntlPts;
  103.     [textMetrics insertElement:(void *)&metrics at:i];
  104.     outlineLen += nCntlPts;
  105.     nLoopsInPath += nLoops;
  106.     }
  107.     theLoops = (int *)NXZoneMalloc([self zone], nLoopsInPath*sizeof(int));
  108.     if (!theLoops) return self;
  109.     PSWWW3DTextLoops([currentFont name], [currentFont pointSize], currentText, nLoopsInPath, theLoops);
  110.     charPath = (float *)NXZoneMalloc([self zone], outlineLen*sizeof(float));
  111.     if (!charPath) {
  112.     NXZoneFree([self zone], theLoops);
  113.         theLoops = NULL;
  114.     return self;
  115.     }
  116.     PSWWW3DTextOutline([currentFont name], [currentFont pointSize], currentText, outlineLen, charPath);
  117.     [[outline empty] setNumSlots:outlineLen];
  118.     for (i = 0; i < outlineLen; i++) {
  119.     [outline insertElement:(void *)&charPath[i] at:i];
  120.     }
  121.     [[loops empty] setNumSlots:nLoopsInPath];
  122.     for (i = 0; i < nLoopsInPath; i++) {
  123.     [loops insertElement:(void *)&theLoops[i] at:i];
  124.     }
  125.     NXZoneFree([self zone], charPath);
  126.     charPath = NULL;
  127.     NXZoneFree([self zone], theLoops);
  128.     theLoops = NULL;
  129.     return self;
  130. }
  131.  
  132. - copyControlPoints:(int)nCntlPts startingAt:(int)outlineIdx to:(Storage *)outlineBuffer
  133. {
  134.     int i;
  135.     
  136.     for (i = outlineIdx; i < outlineIdx+nCntlPts; i++) {
  137.     [outlineBuffer addElement:[outline elementAt:i]];
  138.     }
  139.     return self;
  140. }
  141.  
  142. - orderCounters
  143. /*
  144.  * The order of the counters from charpath and pathforall may
  145.  * not be suitable for RiGeneralPolygon(). Here, we ensure
  146.  * any boundary for a character appears before the holes (if any).
  147.  * And yes, this solution is ugly...
  148.  */
  149. {
  150.     Storage *outlineStack, *loopStack, *outlineBuffer, *loopBuffer;
  151.     int loopIdx = 0, outlineIdx = 0;
  152.     int len, i, j, k, nLoops, nCntlPts;
  153.     
  154.     if (!currentText) return self;
  155.     outlineStack = [[Storage alloc] initCount:0
  156.                     elementSize:sizeof(int)
  157.                     description:"i"];
  158.     loopStack = [[Storage alloc] initCount:0
  159.                  elementSize:sizeof(int)
  160.                  description:"i"];
  161.     outlineBuffer = [[Storage alloc] initCount:0
  162.                                      elementSize:sizeof(float)
  163.                                      description:"f"];
  164.     loopBuffer = [[Storage alloc] initCount:0
  165.                                   elementSize:sizeof(int)
  166.                                   description:"i"];
  167.     len = strlen(currentText);
  168.     for (i = 0; i < len; i++) {
  169.     WW3DTextMetrics metrics = *(WW3DTextMetrics *)[textMetrics elementAt:i];
  170.     BOOL haveInitial;
  171.  
  172.     [[outlineStack empty] setNumSlots:0];
  173.     [[loopStack empty] setNumSlots:0];
  174.     nLoops = metrics.numLoops;
  175.     haveInitial = NO;
  176.     for (j = 0; j < nLoops; j++) {
  177.         float x0, y0, x1, y1, x2, y2;
  178.         
  179.         nCntlPts = *(int *)[loops elementAt:loopIdx+j];
  180.         x0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-4];
  181.         y0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-3];
  182.         x1 = *(float *)[outline elementAt:outlineIdx];
  183.         y1 = *(float *)[outline elementAt:outlineIdx+1];
  184.         x2 = *(float *)[outline elementAt:outlineIdx+2];
  185.         y2 = *(float *)[outline elementAt:outlineIdx+3];
  186.         if (IsCounter(x0, y0, x1, y1, x2, y2)) {
  187.         if (!haveInitial) {
  188.             [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer];
  189.             [loopBuffer addElement:(void *)&nCntlPts];
  190.             if ([outlineStack count] > 0) {
  191.             int count = [outlineStack count];
  192.             
  193.             for (k = 0; k < count; k++) {
  194.                 [self copyControlPoints:*(int *)[loopStack elementAt:k]
  195.                       startingAt:*(int *)[outlineStack elementAt:k]
  196.                   to:outlineBuffer];
  197.                 [loopBuffer addElement:[loopStack elementAt:k]];
  198.             }
  199.             [[outlineStack empty] setNumSlots:0];
  200.             [[loopStack empty] setNumSlots:0];
  201.             }
  202.             haveInitial = YES;
  203.         } else {
  204.             [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer];
  205.             [loopBuffer addElement:(void *)&nCntlPts];
  206.         }
  207.         } else {
  208.         if (!haveInitial) {
  209.             [outlineStack addElement:(void *)&outlineIdx];
  210.             [loopStack addElement:(void *)&nCntlPts];
  211.         } else {
  212.             [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer];
  213.             [loopBuffer addElement:(void *)&nCntlPts];
  214.         }
  215.         }
  216.         outlineIdx += nCntlPts;
  217.     }
  218.     if ([outlineStack count] > 0) {
  219.         int count = [outlineStack count];
  220.         
  221.         for (k = 0; k < count; k++) {
  222.         [self copyControlPoints:*(int *)[loopStack elementAt:k]
  223.             startingAt:*(int *)[outlineStack elementAt:k]
  224.             to:outlineBuffer];
  225.         [loopBuffer addElement:[loopStack elementAt:k]];
  226.         }
  227.     }
  228.     loopIdx += metrics.numLoops;
  229.     }
  230.     [loopStack free];
  231.     loopStack = nil;
  232.     [outlineStack free];
  233.     outlineStack = nil;
  234.     loops = [loops free];
  235.     outline = [outline free];
  236.     loops = loopBuffer;
  237.     outline = outlineBuffer;
  238.     return self;
  239. }
  240.  
  241. - (Storage *)glyphBoundaries:(WW3DTextMetrics *)metrics :(int)loopIdx :(int)outlineIdx
  242. {
  243.     Storage *boundaries;
  244.     int i = 0, nLoops, nCntlPts;
  245.     
  246.     boundaries = [[Storage alloc] initCount:0
  247.                   elementSize:sizeof(int)
  248.                   description:"i"];
  249.     nLoops = metrics->numLoops;
  250.     if (!nLoops) return boundaries;
  251.     nCntlPts = *(int *)[loops elementAt:loopIdx];
  252.     [boundaries addElement:(void *)&i];
  253.     outlineIdx += nCntlPts;
  254.     for (i = 1; i < nLoops; i++) {
  255.     int nCntlPts = *(int *)[loops elementAt:loopIdx+i];
  256.     float x0, y0, x1, y1, x2, y2;
  257.     
  258.     x0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-4];
  259.     y0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-3];
  260.     x1 = *(float *)[outline elementAt:outlineIdx];
  261.     y1 = *(float *)[outline elementAt:outlineIdx+1];
  262.     x2 = *(float *)[outline elementAt:outlineIdx+2];
  263.     y2 = *(float *)[outline elementAt:outlineIdx+3];
  264.     if (IsCounter(x0, y0, x1, y1, x2, y2)) {
  265.         [boundaries addElement:(void *)&i];
  266.     }
  267.     outlineIdx += nCntlPts;
  268.     }
  269.     return boundaries;
  270. }
  271.  
  272. - (int)boundaryAfter:(int)idx in:(Storage *)boundaries withNumLoops:(int)numLoops
  273. {
  274.     int count = [boundaries count];
  275.     return (idx == count-1) ? numLoops : *(int *)[boundaries elementAt:idx+1];
  276. }
  277.  
  278. - readCharPath:(const char *)theText usingFont:(Font *)theFont
  279. {
  280.     if (!theText) return self;
  281.     if (currentText) NXZoneFree([self zone], currentText);
  282.     currentText = NXCopyStringBufferFromZone(theText, [self zone]);
  283.     currentFont = theFont;
  284.     [self getCharPath];
  285.     [self orderCounters];
  286.     return self;
  287. }
  288.  
  289. - generateRIBCommandList
  290. /*
  291.  * Okay, so the idea here is to grovel over each character in the
  292.  * string, placing it, in order, into a RIBCommandList.  After we've
  293.  * generated all the characters, we need to translate it so that it's
  294.  * centered around the origin, and then we need to scale it so that it's
  295.  * some reasonable size.  As a purely ad hoc decision, I'll designate 72
  296.  * pts as 1.0, and work from there.  Once we have the translation and
  297.  * scaling info, we'll malloc up the appropriate RIBTransformBegin,
  298.  * RIBTranslate, RIBScale, and RIBTransformEnd objects to the appropriate
  299.  * places in the RIBCommandList.  Hmm... do we need those to be objects?
  300.  * Think about it this way: when we dump this scene out, do we spit out
  301.  * a set of tokens containing the atomic rib commands that compose this object,
  302.  * or do we spit out some new token corresponding to this WW3DText object?
  303.  * I think the answer is pretty obviously the latter.  Given that, it
  304.  * doesn't even make a whole lot of sense to use a RIBCommandList to hold
  305.  * our polygon info, except for the fact that it makes certain operations
  306.  * easier to understand (bounding boxes, etc.).  There is some performance
  307.  * hit, but let's ignore that for now.
  308.  *
  309.  * well, the reason for a compound command like this to represent itself
  310.  * as a set of objects is so that it can be motion blurred.  By not making
  311.  * the internals of the command opaque, we make it possible for an EveCommand
  312.  * to have a chance of motion blurring this command, the same way it could
  313.  * an atomic RIB command.  Remember, the point of a compound command is
  314.  * so that we can motion blur this command with other versions of itself.
  315.  */
  316. {
  317.     int       i, j, k, len, loopIdx = 0, outlineIdx = 0;
  318.     RtBound   polygonsBoundingBox;
  319.     RtFloat   polygonsCentroid[3], scaleFactor;
  320.     id        transformBeginCommand, transformEndCommand, 
  321.               translateCommand, scaleCommand;
  322.     char *splitText, *curWord;
  323.     
  324.     splitText = NXCopyStringBufferFromZone(currentText, [self zone]);
  325.     curWord = strtok(splitText, " \t\r\n\v\f");
  326.  
  327.     // I should make sure there is some non-whitespace text before I waste my time...
  328.     [ribCommandList empty];
  329.     len = strlen(currentText);
  330.     for (i = 0; i < len; i++) {
  331.     WW3DTextMetrics metrics = *(WW3DTextMetrics *)[textMetrics elementAt:i];
  332.     RIBArchiveRecord *archiveRecord;
  333.     Storage *boundaries;
  334.     int count;
  335.  
  336.     if (NXIsSpace(currentText[i])) {
  337.         curWord = strtok(NULL, " \t\r\n\v\f");
  338.     }
  339.     
  340.     boundaries = [self glyphBoundaries:&metrics :loopIdx :outlineIdx];
  341.     count = [boundaries count];
  342.     
  343.     /*
  344.      * Provide an RiArchiveRecord describing the character we are about to model:
  345.      *
  346.      *    # 'W' (of ``WavesWorld'') in Helvetica 12 point
  347.      */
  348.     if (curWord && count > 0) {
  349.         char *comment;
  350.         int recLen;
  351.         
  352.         recLen = strlen(curWord) + strlen([currentFont name]) + PadComment;
  353.         comment = (char *)NXZoneMalloc([self zone], sizeof(char)*recLen);
  354.         sprintf(comment, " '%c' (of ``%s'') in %s %d point", currentText[i],
  355.                                                                  curWord,
  356.                                                                  [currentFont name],
  357.                                                                  (int)[currentFont pointSize]);
  358.         archiveRecord = [[RIBArchiveRecord alloc] init];
  359.         [archiveRecord setType:RI_COMMENT format:comment];
  360.         [ribCommandList addObject:archiveRecord];
  361.         NXZoneFree([self zone], comment);
  362.     }
  363.     
  364.     for (j = 0; j < count; j++) {
  365.         RIBGeneralPolygon *genP = [[RIBGeneralPolygon alloc] init];
  366.         RtInt *loopsVertices = NULL, *lvp;
  367.         RtFloat *vertices = NULL, *vp;
  368.         RtToken *theTokens;
  369.         RtPointer *parms;
  370.         int nLoops, nVertices = 0;
  371.         int theBoundary, nextBoundary;
  372.         char **archiveV, archiveInfo[80];
  373.             int  printfTypeV[1], printfNV[1];
  374.  
  375.         
  376.         theBoundary = *(int *)[boundaries elementAt:j];
  377.         nextBoundary = [self boundaryAfter:j in:boundaries withNumLoops:metrics.numLoops];
  378.         nLoops = nextBoundary - theBoundary;
  379.         loopsVertices = (RtInt *)NXZoneMalloc([self zone], nLoops*sizeof(RtInt));
  380.         if (!loopsVertices) {
  381.         [genP free];
  382.                 genP = nil;
  383.         return self;
  384.         }
  385.         lvp = loopsVertices;
  386.         for (k = theBoundary; k < nextBoundary; lvp++, k++) {
  387.         *lvp = (*(int *)[loops elementAt:loopIdx+k])>>1;
  388.         nVertices += *lvp;
  389.         }
  390.         vertices = (RtFloat *)NXZoneMalloc([self zone], nVertices*3*sizeof(RtFloat));
  391.         if (!vertices) {
  392.         [genP free];
  393.                 genP = nil;
  394.         NXZoneFree([self zone], loopsVertices);
  395.                 loopsVertices = NULL;
  396.         return self;
  397.         }
  398.         vp = vertices;
  399.         for (k = theBoundary; k < nextBoundary; k++) {
  400.         int s, nCntlPts = *(int *)[loops elementAt:loopIdx+k];
  401.         for (s = outlineIdx; s < nCntlPts+outlineIdx; s += 2) {
  402.             *vp++ = (RtFloat)(*(float *)[outline elementAt:s]);
  403.             *vp++ = (RtFloat)(*(float *)[outline elementAt:s+1]);
  404.             *vp++ = (RtFloat)0.0;
  405.         }
  406.         outlineIdx += nCntlPts;
  407.         }
  408.         archiveV = (char **)NXZoneMalloc([self zone], sizeof(char *));
  409.         sprintf(archiveInfo, "[%df]", nVertices*3);
  410.         *archiveV = NXCopyStringBuffer(archiveInfo);
  411.         theTokens = (RtToken *)NXZoneMalloc([self zone], sizeof(RtToken));
  412.         *theTokens = RI_P;
  413.         parms = (RtPointer *)NXZoneMalloc([self zone], sizeof(RtPointer));
  414.         *parms = (RtPointer)vertices;
  415.             printfTypeV[0] = WW_POINT;
  416.             printfNV[0] = 3*nVertices;
  417.         [genP setNLoops:nLoops
  418.           nVertices:loopsVertices
  419.           n:1
  420.           tokens:theTokens
  421.           parms:parms
  422.           archiveVector:archiveV
  423.                   printfTypeVector:printfTypeV printfNVector:printfNV];
  424.             [genP setMyShape:myShape];
  425.         [ribCommandList addObject:genP];
  426.     }
  427.     loopIdx += metrics.numLoops;
  428.     [boundaries free];
  429.         boundaries = nil;
  430.     }
  431.  
  432.     /*
  433.      * okay, now we have a RIBCommandList that has all the polygons in
  434.      * it.  We need to get it's bounding box and calculate how we should
  435.      * translate it appropriately to (left, right, center) justify it.
  436.      */
  437.     if (![ribCommandList hasBoundingBox])
  438.     {  // see if our text is just spaces.  If it's not, this is a bug...
  439.        if (currentText)  // okay, we're pointing at something
  440.        {  int  i = 0, howMany = strlen(currentText);
  441.  
  442.           while (isspace(currentText[i]) && i < howMany) { i++; }
  443.           if (i != howMany)
  444.           {  NXLogError("WW3DText: although my currentText is not just spaces (it's <%s>), my polygons have no boundingBox - bailing...");
  445.              [self free];
  446.              return nil;
  447.        }
  448.           else
  449.       {  return self;  // we're blank; we're done
  450.        }
  451.        }
  452.        // okay, we've just got no text in us. (there's a song here somewhere...)
  453.        polygonsBoundingBox[0] = 0;
  454.        polygonsBoundingBox[1] = 0;
  455.        polygonsBoundingBox[2] = 0;
  456.        polygonsBoundingBox[3] = 0;
  457.        polygonsBoundingBox[4] = 0;
  458.        polygonsBoundingBox[5] = 0;
  459.     }
  460.     else
  461.     {  N3D_CopyBound(*([ribCommandList boundingBoxStartingAt:0 endingAt:0]), polygonsBoundingBox);
  462.     }
  463.     if (justification == NX_LEFTALIGNED)
  464.     {  polygonsCentroid[0] = 0;
  465.        polygonsCentroid[1] = 0;
  466.        polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); 
  467.     }
  468.     if (justification == NX_CENTERED)
  469.     {  polygonsCentroid[0] = -1 * (polygonsBoundingBox[0] + ((polygonsBoundingBox[1] - polygonsBoundingBox[0])/2.0)); 
  470.        polygonsCentroid[1] = -1 * (polygonsBoundingBox[2] + ((polygonsBoundingBox[3] - polygonsBoundingBox[2])/2.0)); 
  471.        polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); 
  472.     }
  473.     if (justification == NX_RIGHTALIGNED)
  474.     {  polygonsCentroid[0] = -1 * (polygonsBoundingBox[0] + ((polygonsBoundingBox[1] - polygonsBoundingBox[0])));
  475.        polygonsCentroid[1] = -1 * (polygonsBoundingBox[2] + ((polygonsBoundingBox[3] - polygonsBoundingBox[2]))); 
  476.        polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); 
  477.     }
  478.     // we now want to build up a RIBTranslate object which will move that center to (0,0,0)
  479.     translateCommand = [[RIBTranslate alloc] init];
  480.     [translateCommand setDX:polygonsCentroid[0] dy:polygonsCentroid[1] dz:polygonsCentroid[2]];
  481.     [translateCommand setMyShape:myShape];
  482.  
  483.     // now we need to find out how big the font we're using is, so we can scale ourselves appropriately
  484.     // we want 72 pts to equal 1 in Y.
  485.     if (polygonsBoundingBox[3] != polygonsBoundingBox[2])
  486.     {  scaleFactor = ([currentFont pointSize]/72.0) * (1./(polygonsBoundingBox[3] - polygonsBoundingBox[2]));
  487.     }
  488.     else
  489.     {  scaleFactor = 1;
  490.     }
  491.     scaleCommand = [[RIBScale alloc] init];
  492.     [scaleCommand setSX:scaleFactor sy:scaleFactor sz:scaleFactor];
  493.     [scaleCommand setMyShape:myShape];
  494.  
  495.     transformBeginCommand = [[[RIBTransformBegin alloc] init] setMyShape:myShape];
  496.     transformEndCommand = [[[RIBTransformEnd alloc] init] setMyShape:myShape];
  497.  
  498.     // okay, now let's insert the various RIBCommands in the right place in the list.
  499.     // we want: {RIBTransformBegin, RIBScale, RIBTranslate, ... RIBGeneralPolygon ... RIBTransformEnd}
  500.     // "wait a minute!", I hear you saying.  "Don't we need to translate before we do the scale?" 
  501.     // Yes, but remember that RenderMan does things backwards, so this is actually correct...
  502.     [ribCommandList addObject:transformEndCommand];
  503.     [ribCommandList insertObject:translateCommand at:0];
  504.     [ribCommandList insertObject:scaleCommand at:0];
  505.     [ribCommandList insertObject:transformBeginCommand  at:0];
  506.  
  507.     /*
  508.      * Remembering to release the splitText.
  509.      */
  510.     NXZoneFree([self zone], splitText);
  511.     return self;
  512. }
  513.  
  514. @end
  515.  
  516. @implementation WW3DText
  517. static const char *InvalidJustification = "%s: %d is an invalid value for justification: only NX_LEFTALIGNED, NX_RIGHTALIGNED, NX_CENTERED are valid.\n";
  518.  
  519. + initialize { return [WW3DText setVersion:2], self; }
  520.  
  521. - init
  522. {
  523.     [super init];
  524.     currentText = NULL;
  525.     outline = [[Storage alloc] initCount:0
  526.                                elementSize:sizeof(float)
  527.                                description:"f"];
  528.     textMetrics = [[Storage alloc] initCount:0
  529.                                    elementSize:sizeof(WW3DTextMetrics)
  530.                        description:"{iii}"];
  531.     loops = [[Storage alloc] initCount:0
  532.                              elementSize:sizeof(int)
  533.                              description:"i"];
  534.     ribCommandList  = [[RIBCommandList alloc] init];
  535.     justification = NX_CENTERED;
  536.     dirtyBoundingBox = YES;
  537.     return self;
  538. }
  539.  
  540. - awake
  541. {
  542.     [super awake];
  543.     dirtyBoundingBox = YES;
  544.     return self;
  545. }
  546.  
  547. - initWithCharPath:(const char *)theText
  548.     usingFont:(Font *)theFont
  549.     myShape:newMyShape
  550.     justification:(int)newJustification
  551. /*
  552.  * This is the designated initializer for WW3DText. It returns a WW3DText
  553.  * object which conforms to the WWRenderable protocol.
  554.  */
  555. {
  556.     [self init];
  557.     if ((newJustification != NX_LEFTALIGNED) &&
  558.     (newJustification != NX_RIGHTALIGNED) &&
  559.     (newJustification != NX_CENTERED)) {
  560.     NXLogError(InvalidJustification, [WW3DText name], newJustification);
  561.     } else {
  562.     justification = newJustification;
  563.     }
  564.     myShape = newMyShape;
  565.     [self readCharPath:theText usingFont:theFont];
  566.     [self generateRIBCommandList];
  567.     return self;
  568. }
  569.  
  570.  
  571. - setCharPath:(const char *)theText usingFont:(Font *)theFont myShape:newMyShape justification:(int)newJustification
  572. {
  573.     // in case we pop out, wannt zero these guys out ASAP...
  574.     currentText = NULL;
  575.     outline = nil;
  576.     textMetrics = nil;
  577.     loops = nil;
  578.  
  579.  
  580.     outline = [[Storage alloc] initCount:0
  581.                                elementSize:sizeof(float)
  582.                                description:"f"];
  583.     textMetrics = [[Storage alloc] initCount:0
  584.                                    elementSize:sizeof(WW3DTextMetrics)
  585.                        description:"{iii}"];
  586.     loops = [[Storage alloc] initCount:0
  587.                              elementSize:sizeof(int)
  588.                              description:"i"];
  589.     dirtyBoundingBox = YES;
  590.  
  591.     if ((newJustification != NX_LEFTALIGNED) &&
  592.     (newJustification != NX_RIGHTALIGNED) &&
  593.     (newJustification != NX_CENTERED)) {
  594.     NXLogError(InvalidJustification, [WW3DText name], newJustification);
  595.     } else {
  596.     justification = newJustification;
  597.     }
  598.     myShape = newMyShape;
  599.     // we don't want to wipe out the old version...
  600.     if (theText)
  601.     {  currentText = NXCopyStringBufferFromZone(theText, [self zone]);
  602.     }
  603.     else
  604.     {  currentText = NULL;
  605.     }
  606.     [self readCharPath:theText usingFont:theFont];
  607.     [self generateRIBCommandList];
  608.     return self;
  609. }
  610.  
  611. - free
  612. {
  613.     if (currentText) NX_FREE(currentText);
  614.     [outline free];
  615.     [textMetrics free];
  616.     [loops free];
  617.     //NXLogError("WW3DText %p : freeing list %p", self, ribCommandList);
  618.     [ribCommandList free];
  619.     return [super free];
  620. }
  621.  
  622. - _setRIBCommandList:newRIBCommandList 
  623. {
  624.    ribCommandList = newRIBCommandList; 
  625.    //NXLogError("set WW3DText %p's ribCommandList to %p", self, ribCommandList); 
  626.    return self; 
  627. }
  628.  
  629. - _setOutline:newOutline textMetrics:newTextMetrics loops:newLoops
  630. {
  631.    outline = newOutline;
  632.    textMetrics = newTextMetrics;
  633.    loops = newLoops;
  634.  
  635.    return self;
  636. }
  637.  
  638. - _setCurrentText:(const char *)newText
  639. {
  640.    currentText = NXCopyStringBuffer(newText);
  641.  
  642.    return self;
  643. }
  644.  
  645. - ribCommandList { return ribCommandList; }
  646.  
  647.  
  648. - copyFromZone:(NXZone *)zone
  649. {
  650.    id   newCopy = [super copyFromZone:zone];
  651.  
  652.  
  653.    [newCopy _setRIBCommandList:[ribCommandList copyFromZone:zone]];
  654.    [newCopy _setOutline:[outline copyFromZone:zone] textMetrics:[textMetrics copyFromZone:zone] loops:[loops copyFromZone:zone]];
  655.    [newCopy _setCurrentText:currentText];
  656.  
  657.    return newCopy;
  658. }
  659.  
  660.  
  661. // for right now, we're not going to support lerping.  The only useful
  662. // lerping would be with the same text changing size, anyway...
  663. - lerpWith:b by:(float)uValue
  664. {
  665.    if (([self class] != [b class]) || (uValue <= 0.0))
  666.    {  return self;
  667.    }
  668.  
  669.    if (uValue >= 1.0)
  670.    {  return b;
  671.    }
  672.    return self;
  673. }
  674.  
  675. - lerpSelfWith:b by:(float)uValue
  676. {
  677.    if (([self class] != [b class]) || (uValue <= 0.0))
  678.    {  return self;
  679.    }
  680.  
  681.    if (uValue >= 1.0)
  682.    {  return b;
  683.    }
  684.  
  685.    return self;
  686. }
  687.  
  688. - (BOOL)isLerpable { return YES; }
  689.  
  690. - (BOOL)hasBoundingBox        { return YES; }
  691.  
  692. // there is a bug in prman 3.4 that makes it muck up doing motion blur
  693. // on GeneralPolygons.  Note that rendrib doesn't have this bug, and
  694. // renders GeneralPolygons in a MotionBegin/End just fine.  Given that
  695. // most people are using prman 3.4, I'm going to have WW3DText say that
  696. // it's not motion blurrable for now...
  697. - (BOOL)isMotionBlurrable    { return YES; }
  698. - (BOOL)isCompoundCommand    { return YES; }
  699.  
  700. - setBoundingBox:(RtBound *)newBoundingBox
  701. {
  702.     N3D_CopyBound(*newBoundingBox, boundingBox);
  703.     return self;
  704. }
  705.  
  706. - calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime  
  707. {  
  708.     dirtyBoundingBox = NO;
  709.     return [self setBoundingBox:[ribCommandList boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]];
  710. }
  711.  
  712. - (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStartTime endingAt:(RtFloat)intervalEndTime
  713.    if (dirtyBoundingBox) 
  714.    {  [self calculateBoundingBoxStartingAt:intervalStartTime endingAt:intervalEndTime];
  715.    }
  716.    return &boundingBox; 
  717. }
  718.  
  719.  
  720. - (float)lastSampleIsAt { return 0.0; }
  721.  
  722. - (unsigned long int)maxSampleBandwidth { return [ribCommandList maxSampleBandwidth]; }
  723.  
  724. - setMyShape:shape
  725. {
  726.     myShape = shape;
  727.     return self;
  728. }
  729.  
  730. - shape
  731. {
  732.     return myShape;
  733. }
  734.  
  735. - renderMaps:(WW3DCamera *)camera
  736.     startingAt:(RtFloat)shutterOpenTime
  737.     endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
  738. {
  739.     return self;
  740. }
  741.  
  742. - renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
  743. {
  744.     return self;
  745. }
  746.  
  747. - renderMaps:(WW3DCamera *)camera
  748.     startingAt:(RtFloat)shutterOpenTime
  749.     endingAt:(RtFloat)shutterCloseTime
  750. {
  751.     return self;
  752. }
  753.  
  754. - renderMaps:(WW3DCamera *)camera
  755. {
  756.     return self;
  757. }
  758.  
  759. - renderSelfAsBox:(WW3DCamera *)camera
  760.     startingAt:(RtFloat)shutterOpenTime
  761.     endingAt:(RtFloat)shutterCloseTime
  762. {
  763.     return [ribCommandList renderSelfAsBox:camera
  764.                startingAt:shutterOpenTime
  765.                endingAt:shutterCloseTime];
  766. }
  767.  
  768. - renderSelf:(WW3DCamera *)camera
  769.     startingAt:(RtFloat)shutterOpenTime
  770.     endingAt:(RtFloat)shutterCloseTime
  771. {
  772.     return [ribCommandList renderSelf:camera
  773.                startingAt:shutterOpenTime
  774.                endingAt:shutterCloseTime];
  775. }
  776.  
  777. - renderSelf:(WW3DCamera *)camera
  778. {
  779.     return [ribCommandList renderSelf:camera];
  780. }
  781.  
  782. - preRenderSelf:(WW3DCamera *)camera
  783.     startingAt:(RtFloat)shutterOpenTime
  784.     endingAt:(RtFloat)shutterCloseTime
  785. {
  786.     return [ribCommandList preRenderSelf:camera
  787.                startingAt:shutterOpenTime
  788.                endingAt:shutterCloseTime];
  789. }
  790.  
  791. - preRenderSelf:(WW3DCamera *)camera
  792. {
  793.     return [ribCommandList preRenderSelf:camera];
  794. }
  795.  
  796. - transformCTM:(WW3DAttributeState *)attributeState
  797.     startingAt:(RtFloat)shutterOpenTime
  798.     endingAt:(RtFloat)shutterCloseTime 
  799. {
  800.     return [ribCommandList transformCTM:attributeState
  801.                            startingAt:shutterOpenTime
  802.                endingAt:shutterCloseTime]; 
  803. }
  804.  
  805.  
  806. // methods to make me look like a compound command:
  807. - (int)count {  return [ribCommandList count]; }
  808. - objectAt:(int)i {  return [ribCommandList objectAt:i]; }
  809.  
  810.  
  811. // WavesWorld archiving:
  812. // writeEve:(NXStream *)stream
  813. // writeScene:(NXStream *)stream
  814.  
  815. - writeEve:(NXStream *)stream atTabLevel:(int)tab
  816. {
  817.    int  i;
  818.     
  819.    for (i = 0; i < tab; i++) 
  820.    {  NXPrintf(stream, "\t");
  821.    }
  822.    NXPrintf(stream, "WW3DText {%s} %f {%s} ", [currentFont name], [currentFont pointSize], currentText); 
  823.    if (justification == NX_LEFTALIGNED)
  824.    {  NXPrintf(stream, "left;");
  825.    } 
  826.    else
  827.    {  if (justification == NX_CENTERED)
  828.       {  NXPrintf(stream, "center;");
  829.       }
  830.       else
  831.       {  if (justification == NX_RIGHTALIGNED)
  832.          {  NXPrintf(stream, "right;");
  833.          }
  834.          else
  835.          {  NXPrintf(stream, ";");
  836.          }
  837.       }
  838.    }
  839.  
  840.    return self;
  841. }
  842.  
  843. - writeScene:(NXStream *)stream atTabLevel:(int)tab
  844. {
  845.    return [self writeEve:stream atTabLevel:tab];
  846. }
  847.  
  848. - write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
  849. {
  850.    int  i;
  851.  
  852.  
  853.    for (i = 0; i < tab; i++)
  854.    {  NXPrintf(stream, "\t");
  855.    }
  856.  
  857.    NXPrintf(stream, "startShape %s; ", [[self class] name]);
  858.    // need tab
  859.    // need index (position in current list)
  860.    NXPrintf(stream, 
  861.         "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
  862.             tab, index); 
  863.    NXPrintf(stream, "  EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {");
  864.    [self writeEve:stream atTabLevel:tab];
  865.    NXPrintf(stream, "};}\n");
  866.   NXPrintf(stream, "endShape;\n");
  867.  
  868.   return self;
  869. }
  870.  
  871. - writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
  872. {
  873.    int  i;
  874.  
  875.  
  876.    for (i = 0; i < tab; i++)
  877.    {  NXPrintf(stream, "\t");
  878.    }
  879.    NXPrintf(stream, "# ");
  880.    [self writeEve:stream atTabLevel:tab];
  881.    NXPrintf(stream, "\n");
  882.  
  883.    return [ribCommandList writeInventorAtTime:currentTime to:stream atTabLevel:tab];
  884. }
  885.  
  886. #define typeVector "@*@@@@"
  887. #define typeValues ¤tFont, ¤tText, &ribCommandList, &outline, &textMetrics, &loops
  888.  
  889. - read:(NXTypedStream *)stream 
  890. {
  891.     int version;
  892.     
  893.     [super read:stream];
  894.     version = NXTypedStreamClassVersion(stream,"WW3DText");
  895.     if (version == 0) NXReadTypes(stream, "i", &version), version = 1;
  896.     if (version == 1)
  897.     {  NXReadTypes(stream, typeVector, typeValues);
  898.        NXReadArray(stream, "f", 6, boundingBox);
  899.        myShape = NXReadObject(stream);
  900.        justification = NX_CENTERED;
  901.     }
  902.     if (version == 2) 
  903.     {  NXReadTypes(stream, typeVector, typeValues);
  904.        NXReadArray(stream, "f", 6, boundingBox);
  905.        myShape = NXReadObject(stream);
  906.        NXReadTypes(stream, "i", &justification);
  907.     }
  908.  
  909.     return self;
  910. }
  911.  
  912. - write:(NXTypedStream *)stream 
  913. {
  914.     [super write:stream];
  915.     NXWriteTypes(stream, typeVector, typeValues);
  916.     NXWriteArray(stream, "f", 6, boundingBox);
  917.     NXWriteObjectReference(stream, myShape);
  918.     NXWriteTypes(stream, "i", &justification);
  919.     return self;
  920. }
  921.  
  922. - (char *)currentText { return currentText; }
  923. - currentFont { return currentFont; }
  924.  
  925. - (BOOL)theSameAs:otherRIBCommand
  926. {
  927.   if (strcmp(currentText, [otherRIBCommand currentText]))
  928.   {  return NO;
  929.   }
  930.   if ([currentFont pointSize] != [[otherRIBCommand currentFont] pointSize])
  931.   {  return NO;
  932.   }
  933.   if (strcmp([currentFont name], [[otherRIBCommand currentFont] name]))
  934.   {  return NO;
  935.   }
  936.  
  937.   return YES;
  938. }
  939.  
  940. - (BOOL)similarTo:otherRIBCommand 
  941. {
  942.   if ([self class] != [otherRIBCommand class])
  943.   {  return NO;
  944.   }
  945.   return YES;
  946. }
  947.  
  948.  
  949. - (BOOL)isMoot
  950. {
  951.    int  i = 0, howMany = strlen(currentText);
  952.  
  953.  
  954.    while (isspace(currentText[i]) && i < howMany) { i++; }
  955.    if (i != howMany)  // okay, I've got some text
  956.    {  return NO;
  957.    }
  958.  
  959.    return YES;
  960. }
  961.  
  962. - (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  {  return [self isMoot];  }
  963.  
  964.  
  965. - (BOOL)pushesOrPopsCTM { return NO; }
  966. - (BOOL)pushesCTM { return NO; }
  967. - (BOOL)popsCTM { return NO; }
  968.  
  969.  
  970. // boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
  971. - class { return [super class]; }
  972. @end
  973.